class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    
    clone() {
        return new Point(this.x, this.y);
    }
    
    inverse() {
        this.x = -this.x;
        this.y = -this.y;
        return this;
    }
    
    add(p) {
        if (p && p instanceof Point) {
            this.x += p.x;
            this.y += p.y;
            return this;
        } else {
            throw new Error("p must be a valid Point instance");
        }
    }
    
    subtract(p) {
        if (p && p instanceof Point) {
            return this.add(p.clone().inverse());
        } else {
            throw new Error("p must be a valid Point instance");
        }        
    }
}

class Vector {
    constructor(p1, p2) {
        if (typeof p2 === "undefined") {
            this.coordinate = p1.clone();
        } else {
            this.coordinate = p2.subtract(p1);
        }
    }
    
    inverse() {
        return new Vector(this.coordinate.inverse());
    }
        
    model() {
        let x = this.coordinate.x,
            y = this.coordinate.y;
        
        return Math.sqrt(x * x + y * y);
    }
    
    add(v) {
        if (v && v instanceof Vector) {
            return new Vector(this.coordinate.add(v.coordinate));
        } else {
            throw new Error("v must be a valid Vector instance");
        }
    }
    
    subtract(v) {        
        if (v && v instanceof Vector) {
            return this.add(v.inverse());
        } else {
            throw new Error("v must be a valid Vector instance");
        }
    }
    
    zoomTo(ratio) {
        if (typeof ratio === "number") {           
            return new Vector(new Point(this.coordinate.x * ratio, this.coordinate.y * ratio));
        } else {
            throw new Error("ratio must be a valid number");
        }
    }
    
    translation(v) {
        if (v && v instanceof Vector) {
            return this.clone(); // 平移不改变向量的值
        }
    }
}

export class Rect {
    constructor(x1, y1, x2, y2) {
        [this.x1, this.y1, this.x2, this.y2] = [x1, y1, x2, y2];
    }
    
    get width() {
        return this.x2 - this.x1;
    }
    get height() {
        return this.y2 - this.y1;
    }
    
    equal(rect) {
        if (rect == null) {
            return false;
        }
        return this.x1 === rect.x1 
            && this.y1 === rect.y1
            && this.x2 === rect.x2
            && this.y2 === rect.y2;
    }
        
    isNull() {
        return this.x2 <= this.x1 || this.y2 <= this.y1; 
    }
    
    checkIntersection(rect) {
        return Math.abs(rect.x1 + rect.x2 - this.x1 - this.x2)
                < this.x2 - this.x1 + rect.x2 - rect.x1
            && 
                Math.abs(rect.y1 + rect.y2 - this.y1 - this.y2)
                < this.y2 - this.y1 + rect.y2 - rect.y1;
    } 
      
    intersect(rect) {
        if (rect == null || rect.isNull()) {
            return null;
        }
        if (this.isNull()) {
            return null;
        }
        
        let r = new Rect(
            Math.max(this.x1, rect.x1),
            Math.max(this.y1, rect.y1),
            Math.min(this.x2, rect.x2),
            Math.min(this.y2, rect.y2)
        );
        
        return r.x1 >= r.x2 || r.y1 >= r.y2 ? null : r;
    }        
       
    subtract(rect) {
        if (rect == null || this.isNull() || rect.isNull()) {
            return [this];
        }
        
        let r = this.intersect(rect);
        if (r == null) {
            return [this];
        }
        if (r.equal(this)) {
            return null;
        }
        
        let _1_x1 = this.x1,
            _1_y1 = this.y1,
            _1_x2 = this.x2,
            _1_y2 = this.y2,
            _r_x1 = r.x1,
            _r_y1 = r.y1,
            _r_x2 = r.x2,
            _r_y2 = r.y2;
        
        //  ___________
        // |_____1_____|
        // |  |     |  |
        // |2 |  r  | 3| rect1
        // |  |_____|  |
        // |__|__4__|__|
        let rest = [
                new Rect(_1_x1, _1_y1, _1_x2, _r_y1), // 1
                new Rect(_1_x1, _r_y1, _r_x1, _1_y2), // 2
                new Rect(_r_x2, _r_y1, _1_x2, _1_y2), // 3
                new Rect(_r_x1, _r_y2, _r_x2, _1_y2)  // 4
        ];
        return rest.filter(r => !r.isNull());
    }
}

const ZOOM_MAX = 8,
      ZOOM_MIN = 0.1;
const VIEWER = {
    NONE: 0,
    ZOOM: 1,
    DRAG: 2,
    WWWL: 3
}

export class Canvas {
    constructor(canvas, width = 512, height = 512,
                 bgcolor = "#000000", clearBeforePaint = true) {
        try {
            this.ctx = canvas.getContext("2d");
            this.canvas = canvas;
        } catch (ex) {
            throw new Error("Not a valid canvas dom node!");
        } 
         
        this.width = width;
        this.height = height;
            
        this.toImage();
         
        
        this.zoomRatio = 1;
        this.operation = VIEWER.NONE;
        
        this.translate = {x: 0, y: 0};
        this.zoomRatio = 1;
        
    }
     
    toImage(format = "jpeg") {
        this.img = new Image();
        this.img.src = this.canvas.toDataURL(`image/${format}`);
        return this.img;
    }
     
    draw() {        
        this.ctx.clearRect(0, 0, this.width, this.width);    
        this.ctx.save();
        this.ctx.translate(this.translate.x, this.translate.y);       
        this.ctx.scale(this.zoomRatio, this.zoomRatio);
        this.ctx.drawImage(this.img, 0, 0);
        this.ctx.restore();        
    }
    
    translateTo(x = 0, y = 0) {       
        if (x === 0 && y === 0) {
            return;
        }
        this.translate.x += x;
        this.translate.y += y;
        this.draw();
    }
    
    zoom(ratio = 1) {         
//        this.ctx.clearRect(0, 0, this.width, this.height);            
//        this.ctx.save();        
//        //this.ctx.translate(this.translate.x, this.translate.y); 
//        this.ctx.translate(this.width / 2, this.height / 2);
//        this.ctx.translate(-this.width / 2 * this.zoomRatio, -this.height / 2 * this.zoomRatio);       
//        this.zoomRatio = ratio;              
//        this.ctx.scale(this.zoomRatio, this.zoomRatio);
//        this.ctx.drawImage(this.img, 0, 0);
//        this.ctx.restore();  
        
        this.ctx.clearRect(0, 0, this.width, this.height);
               
        this.ctx.save();        
        this.zoomRatio = ratio;
        let x = this.translate.x + (1 - ratio) * (this.width / 2 - this.translate.x),
            y = this.translate.y + (1 - ratio) * (this.height / 2 - this.translate.y);
        this.ctx.translate(x, y);
        
        this.ctx.scale(ratio, ratio);
        
        this.ctx.drawImage(this.img, 0, 0);
        this.ctx.restore();
        this.translate.x = x;
        this.translate.y = y;
    }
}